#include "stdafx.h"
#include "Application.h"

#include <google/protobuf/text_format.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/io/tokenizer.h>

/*
var xr, yr, zr, xd, yd, zd, X, Y, Z, Xs, Ys, d;
//the position vector relative to the planet's center.
xr = planet.x - x;
yr = planet.y - y;
zr = planet.z - z;
// Normalizing it (making its magnitude 1):
d = sqrt( xr*xr + yr*yr + zr*zr ); //its current length
xr /= d;     yr /= d;    zr /= d;
//the vector pointing to left/right from the heading direction.
xd = cos(degtorad(direction-90));
yd = -sin(degtorad(direction-90));
zd = 0;  //we don't need to normalize this, its magnitude is already 1.
//the cross-product:
X = yr*zd - zr*yd;
Y = zr*xd - xr*zd;
Z = xr*yd - yr*xd;
//normalizing the cross-product:
d = sqrt( X*X + Y*Y + Z*Z ); //cross-product's length
X /= d;     Y /= d;    Z /= d;
//The scale factors for the first and second vectors:
Xs = sqrt( xr*xr+yr*yr+zr*zr + sqr(speed) ); //xr*xr+yr*yr+zr*zr is sqr(sqrt(xr*xr+yr*yr+zr*zr)) I.E the radius squared.
Ys = speed;
//Calculating the final position vector:
xr *= Xs;   yr *= Xs;   zr *= Xs;
xd *= Ys;   yd *= Ys;   zd *= Ys;
//Storing the vector in the following global variables:
global.Xvector = xr + xd;
global.Yvector = yr + yd;
global.Zvector = zr + zd;
//*/

/*
What rouncED posted was just the rotational part of the matrix and the last entry of the last line should be a 0 rather than a 1. What he aimed at was the general layout of a transformation matrix that boils down to:

right.x   right.y   right.z   0
up.x      up.y      up.z      0
dir.x     dir.y     dir.z     0
pos.x     pos.y     pos.z     1


What you refered to as line from V1V2 in your own post can be considered the dir (direction) of your view. The up is the up vector of your view while right is the right vector of your view. Those three vectors are orthogonal to each other and span your view's coordinate system. 

rouncED refered to right as cross which you can calculate by a vector cross product of dir (your line) and up as he pointed out. What he refered to as crossup is the recalculated up vector build by vector cross product of right (his cross) and dir (your line). Recalculating the up vector this way ensures the three vector right, up, and dir are orthogonal to each other.

But in the end this matrix does help you nothing because it describes the orientation of an object and you cannot build this from your input. Of course you could cheat and invert your line V1V2 (dir) and the right vector because you want to rotate about 180 degrees on the up vector thus effectively inverting the right and dir vectors of your orientation. But this would still lack the transformation of the rotation center to the origin which is imperative for your rotation to take effect at position V1. 

To do what you want to do you need to do what I showed you. If you need a combined matrix of this operation read it back from OpenGL or use your own matrix class to perform the same operations like so:

Matrix4x4 inv_translation = Matrix4x4::Identity();
Matrix4x4 translation = Matrix4x4::Identity();
Matrix4x4 rotation = Matrix4x4::Identity();

inv_translation.SetTranslation(-V1.x, -V1.y, -V1.z);
rotation.BuildRotationMatrixForAxis(180 (degrees), up_vector);
translation.SetTranslation(V1.x, V1.y, V1.z);

Matrix4x4 composed_result = inv_translation * rotation * translation;


The method BuildRotationMatrixForAxis creates a rotation matrix for an arbitrary axis with the given angle in degrees. You can look up the equation in any linear algebra book or Google of course. The up_vector is your view's up axis, which is usually (0, 1, 0) if your camera can't tilt.
//*/

namespace Sphericubic
{
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	Application::Application()
	:	m_hWnd(NULL),
		m_oViewManager(),
		m_vCelestialObjects(),
		m_mCelestialObjects(),
		m_pRenderer(NULL),
		m_pMesh(NULL),
		m_pLastJumpCelObj(NULL),
		m_pLocalSystemCelObj(NULL),
		m_pVisualHelperManager(NULL),
		m_pFontManager(NULL),
		m_oMeshRotation(0.0, 0.0f, 0.0f),
		m_uTickCount(0),
		m_fDeltaTime(0.0f),
		m_fTimeFactor(1.0f),
		m_fSpeedFactor(1.0f),
		m_bSnapToCelObj(false)
	{

	}

	Application::~Application()
	{
		if ( NULL != m_pRenderer )
		{
			delete m_pRenderer;
			m_pRenderer = NULL;
		}
	}

	bool Application::OpenVideo()
	{
		bool bResult = false;
		if ( NULL != m_hWnd )
		{
			if ( NULL == m_pRenderer )
			{
				m_pRenderer = new D3DRenderer( m_hWnd );
			}
			if ( NULL != m_pRenderer )
			{
				bResult = m_pRenderer->OpenVideo( m_oVideoInfo );
			}
			if ( false != bResult )
			{
				m_oViewManager.SetCameraPos( &m_pRenderer->GetCameraPos() );
				m_oViewManager.SetPixelRatio( m_pRenderer->GetPixelSize() );

				m_pRenderer->GetCameraPos().z = -150.0f;
				m_pRenderer->UpdateCamera( false, true );

				m_pVisualHelperManager = new VisualHelperManager( *m_pRenderer );
				Matrix oPlane;
				D3DXMatrixIdentity( &oPlane );
				m_pVisualHelperManager->AddGrid( "x axis", oPlane, 10.0f );

				// font
				m_pFontManager = new FontManager( *m_pRenderer );
				FontManager::FontInfo oFontInfo = { L"Arial Narrow", 12, FW_NORMAL, false };
				m_pFontManager->AddFont( "default", oFontInfo );

				// fx
				bResult = m_pRenderer->LoadEffect( "default", L"Data/Effects/Sphericubic.fx" ); //&& CreateMesh( 10, 40.0f );
			}
		}
		return bResult;
	}

	void Application::CloseVideo()
	{
		if ( NULL != m_pFontManager )
		{
			m_pFontManager->RemoveFont( "default" );
			delete m_pFontManager;
			m_pFontManager = NULL;
		}
		if ( NULL != m_pVisualHelperManager )
		{
			delete m_pVisualHelperManager;
			m_pVisualHelperManager = NULL;
		}
		if ( NULL != m_pRenderer )
		{
			ReleaseMesh();
			m_pRenderer->CloseVideo();
		}
	}

	bool Application::CreateMesh(const unsigned int& _uSubdivisions, const float& _fScale)
	{
		bool bResult = false;
		if ( NULL != m_pRenderer )
		{
			if ( NULL == m_pMesh )
			{
				m_pMesh = new SphericubicMesh( CelestialObject::EType_PLANET, *m_pRenderer );
			}
			if ( NULL != m_pMesh )
			{
				bResult = m_pMesh->Create( _uSubdivisions, _fScale );
				if ( false != bResult )
				{
					Matrix oTemp;
					D3DXMatrixTranslation( &oTemp, 0.0f, 1.0f, 0.0f );
					Matrix oTemp2;
					D3DXMatrixRotationZ( &oTemp2, 45.0f / 2.0f * s_DegToRad );
					Matrix oTemp3;
					D3DXMatrixMultiply( &oTemp3, &oTemp, &oTemp2 );
					m_pMesh->SetRotationAxisToSelf( Vector3( oTemp3._41, oTemp3._42, oTemp3._43 ) );

					m_pMesh->SetRotationSpeedToSelf( 360.0f / 60.0f );
				}
			}
		}
		return bResult;
	}

	void Application::ReleaseMesh()
	{
		if ( NULL != m_pMesh )
		{
			m_pMesh->Release();
			delete m_pMesh;
			m_pMesh = NULL;
		}
	}

	bool Application::LoadCelestialData(const string& _strFilename)
	{
		bool bResult = false;

		// no load if we already have data
		if ( false != m_vCelestialObjects.empty() )
		{
			ifstream oInputStream;
			oInputStream.open( _strFilename.c_str() );
			bResult = oInputStream.is_open();

			if ( false != bResult )
			{
				// protocol buffers loading error handling
				class EC : public io::ErrorCollector
				{
				public:
					EC() :	io::ErrorCollector() { };
					virtual ~EC() { };
					virtual void AddError(int line, int column, const string& message)
					{
						size_t uNbChars = 0;
						wchar_t wszBuffer[1024];
						const char* pSrc = message.c_str();
						mbsrtowcs_s( &uNbChars, wszBuffer, 1024, &pSrc, message.length(), NULL );
						OutputDebugString( wszBuffer );
						OutputDebugString( L"\n" );
					}
				};

				io::IstreamInputStream oPBInputStream( &oInputStream );
				PBOrbitingObjectArray oData;
				TextFormat::Parser oParser;
				EC oEC;
				oParser.RecordErrorsTo( &oEC );
				bResult = oParser.Parse( &oPBInputStream, &oData );
				oInputStream.close();
				if ( false != bResult )
				{
					const int sCount = oData.object_size();
					for ( int sIndex = 0 ; sCount > sIndex ; ++sIndex )
					{
						const PBOrbitingObject& rOrbitingObj = oData.object( sIndex );
						bResult = CreateOrbitingObject( rOrbitingObj );
						if ( false == bResult )
						{
							break;
						}
					}
				}
				if ( false != bResult )
				{
					m_oViewManager.SetRenderer( m_pRenderer );
				}
			}
		}

		return bResult;
	}

	void Application::UnloadCelestialData()
	{
		CelestialObjectPtrVec::iterator iPtr = m_vCelestialObjects.begin();
		while ( m_vCelestialObjects.end() != iPtr )
		{
			delete *iPtr;
			m_vCelestialObjects.erase( iPtr );
			iPtr = m_vCelestialObjects.begin();
		}
		m_mCelestialObjects.clear();
		m_pLastJumpCelObj = NULL;
		m_pLocalSystemCelObj = NULL;
	}

	void Application::JumpToCelestialObject(const string& _strName)
	{
		m_pLastJumpCelObj = GetCelestialObject( _strName );
		if ( NULL != m_pLastJumpCelObj )
		{
			m_pLastJumpCelObj->Update( 0.0f );
			const Matrix& oMatPos = m_pLastJumpCelObj->GetWorldMatrix();
			m_pRenderer->GetCameraPos() = Vector3( oMatPos._41 + m_pLastJumpCelObj->GetMaxSize(), oMatPos._42, oMatPos._43 );
			m_pRenderer->GetCameraRot() = Vector3( 0.0f, -90.0f, 0.0f );
			m_pRenderer->UpdateCamera( true, true );

			m_pLocalSystemCelObj = m_pLastJumpCelObj;
			while ( ( NULL != m_pLocalSystemCelObj->GetParent() )
				&& ( CelestialObject::EType_SYSTEM != m_pLocalSystemCelObj->GetType() ) )
			{
				m_pLocalSystemCelObj = m_pLocalSystemCelObj->GetParent();
			}
		}
	}

	bool Application::Update()
	{
		// update time vars
		DWORD uTickCount = GetTickCount();
		if ( 0 != m_uTickCount )
		{
			m_fDeltaTime = ( uTickCount - m_uTickCount ) / 1000.0f;
		}
		if ( 0.0f != m_fDeltaTime )
		{
			unsigned int a = 0;
			++a;
		}
		m_uTickCount = uTickCount;

		// update input data
		InputManager& rIM = InputManager::GetInstance();
		rIM.Update();

		if ( NULL != m_pRenderer )
		{
			{
				InputManager::RawKeyboardInfo oRKI;
				InputManager::RawMouseInfo oRMI;
				rIM.GetRawKeyboardInfo( oRKI );
				rIM.GetRawMouseInfo( oRMI );

				// speed acceleration
				{
					const bool bAccSpeed = rIM.JustReleasedKey( DIK_ADD );
					const bool bDecSpeed = rIM.JustReleasedKey( DIK_SUBTRACT );
					const bool bResetSpeed = rIM.JustReleasedKey( DIK_NUMPADENTER );
					if ( false != bAccSpeed )
					{
						m_fSpeedFactor += m_fSpeedFactor;
					}
					if ( false != bDecSpeed )
					{
						m_fSpeedFactor /= 2.0f;
					}
					if ( false != bResetSpeed )
					{
						m_fSpeedFactor = 1.0f;
					}
				}

				// time acceleration
				{
					const bool bAccTime = rIM.JustReleasedKey( DIK_MULTIPLY );
					const bool bDecTime = rIM.JustReleasedKey( DIK_DIVIDE );
					if ( false != bAccTime )
					{
						m_fTimeFactor += m_fTimeFactor;
					}
					if ( false != bDecTime )
					{
						m_fTimeFactor /= 2.0f;
					}
				}

				m_bSnapToCelObj = rIM.JustReleasedKey( DIK_TAB ) ? !m_bSnapToCelObj : m_bSnapToCelObj;

				Vector3& rCamRot = m_pRenderer->GetCameraRot();
				Vector3& rCamPos = m_pRenderer->GetCameraPos();

				if ( ( false != m_bSnapToCelObj ) && ( NULL != m_pLastJumpCelObj ) && ( CelestialObject::EType_PLANET == m_pLastJumpCelObj->GetType() ) )
				{
					// planet camera
					const Matrix& oCelObjWorld = m_pLastJumpCelObj->GetWorldTransform();
					const Vector3 oCelObjPos = Vector3( oCelObjWorld._41, oCelObjWorld._42, oCelObjWorld._43 );
					Vector3 oUpDir = rCamPos - oCelObjPos;
					D3DXVec3Normalize( &oUpDir, &oUpDir);
					rCamPos = oCelObjPos + oUpDir * m_pLastJumpCelObj->GetMaxSize() / 2.0f;
				}
				else
				{
					// free camera
					if ( ( 0 != oRMI.rgbButtons[0] ) || ( 0 != oRMI.rgbButtons[1] ) || ( 0 != oRMI.rgbButtons[2] ) )
					{
						rCamRot.y += oRMI.lX * m_fDeltaTime * 10.0f;
						rCamRot.x += oRMI.lY * m_fDeltaTime * 10.0f;
					}

					const float fBoost = ( oRKI[DIK_LSHIFT] | oRKI[DIK_RSHIFT] ) ? m_fSpeedFactor : 1.0f;
					const float fCameraSpeed = m_fDeltaTime * m_fSpeedFactor;

					Vector3 oFrontDir;
					Vector3 oRightDir;
					Vector3 oUpDir;
					m_pRenderer->GetCameraDir( oFrontDir, oRightDir, oUpDir, true );

					rCamPos += oFrontDir * ( oRKI[DIK_UP] | oRKI[DIK_W] ? 1.0f : 0.0f ) * fCameraSpeed;
					rCamPos -= oFrontDir * ( oRKI[DIK_DOWN] | oRKI[DIK_S] ? 1.0f : 0.0f ) * fCameraSpeed;
					rCamPos += oRightDir * ( oRKI[DIK_RIGHT] | oRKI[DIK_D] ? 1.0f : 0.0f ) * fCameraSpeed;
					rCamPos -= oRightDir * ( oRKI[DIK_LEFT] | oRKI[DIK_A] ? 1.0f : 0.0f ) * fCameraSpeed;
					rCamPos.y += ( oRKI[DIK_SPACE] ? 1.0f : 0.0f ) * fCameraSpeed;
					m_pRenderer->UpdateCamera( true, true );

					m_pFontManager->Printf( "default", 0, 0, L"Pos : %10.2f %10.2f %10.2f", rCamPos.x, rCamPos.y, rCamPos.z );
				}

				m_pFontManager->Printf( "default", 0, 14, L"Speed : %d(%d km/h)", (int)m_fSpeedFactor, (int)( m_fSpeedFactor * 3.6f ) );

				memcpy( &m_oRKI, &oRKI, sizeof( InputManager::RawKeyboardInfo ) );
				memcpy( &m_oRMI, &oRMI, sizeof( InputManager::RawMouseInfo ) );
			}

			// mesh update
			if ( NULL != m_pMesh )
			{
				m_pMesh->Update( m_fDeltaTime );

				// put mesh in the render queue with specified effect
				m_pRenderer->RenderRequest( m_pMesh, "default" );
			}

			if ( NULL != m_pLocalSystemCelObj )
			{
				// update current local system objects position and orientation.
				m_pLocalSystemCelObj->Update( m_fDeltaTime * m_fTimeFactor );
				// determine visibility for current local system objects.
				m_oViewManager.Update( *m_pLocalSystemCelObj );
			}

			//m_pRenderer->RenderRequest( m_pVisualHelperManager, "default" );
			m_pRenderer->RenderRequest( m_pFontManager, "default" );

			// render scene
			m_pRenderer->Begin();
			m_pRenderer->Process();
			m_pRenderer->End();
		}

		return true;
	}

	bool Application::CreateOrbitingObject(const Message& _rMessage)
	{
		bool bResult = false;

		const float fDistanceScale = 1.0f; //0.0001f;
		const float fTimeScale = 1.0f / ( 60.0f * 60.0f );

		const PBOrbitingObject& rPBOrbitingObj = static_cast<const PBOrbitingObject&>( _rMessage );
		if ( ( false != rPBOrbitingObj.has_name() )			// unique name is required
			&& ( false != rPBOrbitingObj.has_type() )		// type is required
			&& ( false != rPBOrbitingObj.has_radius() ) )	// radius is required
		{
			DataKey uKey = 0;
			MakeKey( uKey, rPBOrbitingObj.name() );

			// check name uniqueness
			bResult = ( m_mCelestialObjects.end() == m_mCelestialObjects.find( uKey ) );
			if ( false != bResult )
			{
				const CelestialObject::EType eCelObjType = PBCelObjTypeToCelObjType( rPBOrbitingObj.type() );
				OrbitingObject* pOrbitingObj = NULL;
				switch ( rPBOrbitingObj.type() )
				{
					case PBOrbitingObject::EObjectType_PLANET:
					case PBOrbitingObject::EObjectType_STAR:
					case PBOrbitingObject::EObjectType_ASTEROID:
					case PBOrbitingObject::EObjectType_COMET:
					{
						pOrbitingObj = new SphericubicMesh( eCelObjType, *m_pRenderer );
						break;
					}
					default:
					{
						pOrbitingObj = new OrbitingObject( eCelObjType );
						break;
					}
				}

				bResult = ( NULL != pOrbitingObj );
				if ( false != bResult )
				{
					m_mCelestialObjects[uKey] = pOrbitingObj;
					m_vCelestialObjects.push_back( pOrbitingObj );

					// speed to self
					if ( false != rPBOrbitingObj.has_speed_to_self() )
					{
						const float fAngleSpeed = rPBOrbitingObj.speed_to_self() / rPBOrbitingObj.radius();
						pOrbitingObj->SetRotationSpeedToSelf( fAngleSpeed * s_RadToDeg * fTimeScale );
					}
					else
					{
						pOrbitingObj->SetRotationSpeedToSelf( 0.0f );
					}

					// rotation to self
					if ( false != rPBOrbitingObj.has_orientation_to_self() )
					{
						Vector3 oRotationAxis;
						ComputeRotationAxis( oRotationAxis,
							rPBOrbitingObj.orientation_to_self().yaw(),
							rPBOrbitingObj.orientation_to_self().pitch(),
							rPBOrbitingObj.orientation_to_self().roll() );
						pOrbitingObj->SetRotationAxisToSelf( oRotationAxis );
					}

					// parenting
					if ( ( false != rPBOrbitingObj.has_parent_name() )
						&& ( false != rPBOrbitingObj.has_distance_to_parent() ) )
					{
						uKey = 0;
						MakeKey( uKey, rPBOrbitingObj.parent_name() );
						CelestialObjectPtrMap::iterator iPair = m_mCelestialObjects.find( uKey );

						bResult = ( m_mCelestialObjects.end() != iPair );
						if ( false != bResult )
						{
							// hierarchy assignment
							pOrbitingObj->SetParent( iPair->second );
							iPair->second->AddChild( pOrbitingObj );

							// speed to parent
							if ( false != rPBOrbitingObj.has_speed_to_parent() )
							{
								const float fAngleSpeed = rPBOrbitingObj.speed_to_parent() / rPBOrbitingObj.distance_to_parent();
								pOrbitingObj->SetRotationSpeedToParent( fAngleSpeed * s_RadToDeg * fTimeScale );
							}

							// rotation to parent
							if ( false != rPBOrbitingObj.has_orientation_to_self() )
							{
								Vector3 oRotationAxis;
								ComputeRotationAxis( oRotationAxis,
									rPBOrbitingObj.orientation_to_parent().yaw(),
									rPBOrbitingObj.orientation_to_parent().pitch(),
									rPBOrbitingObj.orientation_to_parent().roll() );
								pOrbitingObj->SetRotationAxisToParent( oRotationAxis );
							}

							// distance to parent
							pOrbitingObj->SetDistanceToParent( rPBOrbitingObj.distance_to_parent() * fDistanceScale );
						}
					}

					if ( false != bResult )
					{
						// class specifics
						switch ( rPBOrbitingObj.type() )
						{
							case PBOrbitingObject::EObjectType_PLANET:
							case PBOrbitingObject::EObjectType_STAR:
							case PBOrbitingObject::EObjectType_ASTEROID:
							case PBOrbitingObject::EObjectType_COMET:
							{
								const unsigned int uDefinition = rPBOrbitingObj.has_definition() ? rPBOrbitingObj.definition() : 25;
								( static_cast<SphericubicMesh*>( pOrbitingObj ) )->Create( uDefinition, rPBOrbitingObj.radius() * fDistanceScale );
								break;
							}
							default:
							{
								break;
							}
						}
					}
				}
			}
		}

		return bResult;
	}

	void Application::ComputeRotationAxis(Vector3& _rRotationAxis, const float& _fYaw, const float& _fPitch, const float& _fRoll) const
	{
		Matrix oTemp;
		Matrix oTemp2;
		Matrix oTemp3;

		D3DXMatrixTranslation( &oTemp, 0.0f, 1.0f, 0.0f );

		D3DXMatrixRotationZ( &oTemp2, _fRoll * s_DegToRad );
		D3DXMatrixMultiply( &oTemp3, &oTemp, &oTemp2 );

		D3DXMatrixRotationX( &oTemp2, _fPitch * s_DegToRad );
		D3DXMatrixMultiply( &oTemp3, &oTemp3, &oTemp2 );

		D3DXMatrixRotationY( &oTemp2, _fYaw * s_DegToRad );
		D3DXMatrixMultiply( &oTemp3, &oTemp3, &oTemp2 );

		_rRotationAxis.x = oTemp3._41;
		_rRotationAxis.y = oTemp3._42;
		_rRotationAxis.z = oTemp3._43;
	}

	CelestialObject* Application::GetCelestialObject(const string& _strName) const
	{
		CelestialObject* pResult = NULL;

		DataKey uKey = 0;
		MakeKey( uKey, _strName );
		CelestialObjectPtrMap::const_iterator iPair = m_mCelestialObjects.find( uKey );

		if ( m_mCelestialObjects.end() != iPair )
		{
			pResult = iPair->second;
		}

		return pResult;
	}

	CelestialObject::EType Application::PBCelObjTypeToCelObjType(const PBOrbitingObject_EObjectType& _ePBCelObj) const
	{
		CelestialObject::EType eCelObj = CelestialObject::EType_UNKNOWN;

		switch ( _ePBCelObj )
		{
			case PBOrbitingObject::EObjectType_PLANET:
			{
				eCelObj = CelestialObject::EType_PLANET;
				break;
			}
			case PBOrbitingObject::EObjectType_STAR:
			{
				eCelObj = CelestialObject::EType_STAR;
				break;
			}
			case PBOrbitingObject::EObjectType_ASTEROID:
			{
				eCelObj = CelestialObject::EType_ASTEROID;
				break;
			}
			case PBOrbitingObject::EObjectType_COMET:
			{
				eCelObj = CelestialObject::EType_COMET;
				break;
			}
			case PBOrbitingObject::EObjectType_AXE:
			{
				eCelObj = CelestialObject::EType_AXE;
				break;
			}
			case PBOrbitingObject::EObjectType_SYSTEM:
			{
				eCelObj = CelestialObject::EType_SYSTEM;
				break;
			}
			case PBOrbitingObject::EObjectType_GALAXY:
			{
				eCelObj = CelestialObject::EType_GALAXY;
				break;
			}
		}

		return eCelObj;
	}

	void Application::OutputDebug(const string& _strText)
	{
		size_t uNbChars = 0;
		wchar_t wszBuffer[1024];
		const char* pSrc = _strText.c_str();
		mbsrtowcs_s( &uNbChars, wszBuffer, 1024, &pSrc, _strText.length(), NULL );
		OutputDebugString( wszBuffer );
		OutputDebugString( L"\n" );
	}
}
